package ace.module.oauth2.server.core.impl.service.impl;

import ace.cmp.i18n.api.MessageSourceAssertService;
import ace.module.oauth2.server.core.impl.converter.Oauth2AuthorizationFromOAuth2AuthorizationConverter;
import ace.module.oauth2.server.core.impl.converter.Oauth2AuthorizationToOAuth2AuthorizationConverter;
import ace.module.oauth2.server.core.impl.dao.entity.Oauth2Authorization;
import ace.module.oauth2.server.core.impl.manager.Oauth2AuthorizationManager;
import ace.module.oauth2.server.core.impl.manager.cache.Oauth2AuthorizationCacheManager;
import java.util.Objects;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.stereotype.Component;

/**
 * @author caspar
 * @date 2023/3/9 9:38 {@link JdbcOAuth2AuthorizationService}
 */
@Slf4j
@AllArgsConstructor
@Component
public class Oauth2AuthorizationServiceImpl implements OAuth2AuthorizationService {
  private final Oauth2AuthorizationManager oauth2AuthorizationManager;
  private final Oauth2AuthorizationCacheManager oauth2AuthorizationCacheManager;
  private final Oauth2AuthorizationFromOAuth2AuthorizationConverter
      oauth2AuthorizationFromOAuth2AuthorizationConverter;
  private final Oauth2AuthorizationToOAuth2AuthorizationConverter
      oauth2AuthorizationToOAuth2AuthorizationConverter;
  private final MessageSourceAssertService assertService;
  private final RegisteredClientRepository registeredClientRepository;

  @Override
  public void save(OAuth2Authorization authorization) {
    Oauth2Authorization oldOauth2Authorization =
        this.oauth2AuthorizationManager.getFromCacheById(authorization.getId());
    Oauth2Authorization dbModel = this.convertWithOldData(authorization, oldOauth2Authorization);
    if (Objects.isNull(oldOauth2Authorization)) {
      oauth2AuthorizationManager.add(dbModel);
    } else {
      oauth2AuthorizationManager.modify(dbModel);
    }
    this.oauth2AuthorizationCacheManager.clear(dbModel);
  }

  @Override
  public void remove(OAuth2Authorization authorization) {
    Oauth2Authorization oldOauth2Authorization =
        this.oauth2AuthorizationManager.getFromCacheById(authorization.getId());
    if (oldOauth2Authorization != null) {
      this.oauth2AuthorizationManager.remove(authorization.getId());
      this.oauth2AuthorizationCacheManager.clear(oldOauth2Authorization);
    }
  }

  @Override
  public OAuth2Authorization findById(String id) {
    Oauth2Authorization dbModel =
        this.oauth2AuthorizationManager.getFromCacheByIdAndDeleteFlagIsFalse(id);
    if (dbModel == null) {
      return null;
    }
    OAuth2Authorization authorization = this.convertToOAuth2Authorization(dbModel);
    return authorization;
  }

  @Override
  public OAuth2Authorization findByToken(String token, OAuth2TokenType tokenType) {
    assertService.isEmpty(token, this.getClass().getName() + ".error-token-not-empty", "token不能为空");
    if (tokenType == null) {
      return this.getByToken(token);
    }
    if (OAuth2ParameterNames.STATE.equals(tokenType.getValue())) {
      return this.getByState(token);
    }
    if (OAuth2ParameterNames.CODE.equals(tokenType.getValue())) {
      return this.getByAuthorizationCodeValue(token);
    }
    if (OAuth2TokenType.ACCESS_TOKEN.equals(tokenType)) {
      return this.getByAccessTokenValue(token);
    }
    if (OAuth2TokenType.REFRESH_TOKEN.equals(tokenType)) {
      return this.getByRefreshTokenValue(token);
    }
    return null;
  }

  private OAuth2Authorization getByRefreshTokenValue(String token) {
    Oauth2Authorization dbModel = this.oauth2AuthorizationManager.getByRefreshTokenValue(token);
    OAuth2Authorization authorization = this.convertToOAuth2Authorization(dbModel);
    return authorization;
  }

  private OAuth2Authorization getByAccessTokenValue(String token) {
    Oauth2Authorization dbModel = this.oauth2AuthorizationManager.getByAccessTokenValue(token);
    OAuth2Authorization authorization = this.convertToOAuth2Authorization(dbModel);
    return authorization;
  }

  private OAuth2Authorization getByAuthorizationCodeValue(String token) {
    Oauth2Authorization dbModel =
        this.oauth2AuthorizationManager.getByAuthorizationCodeValue(token);
    OAuth2Authorization authorization = this.convertToOAuth2Authorization(dbModel);
    return authorization;
  }

  private OAuth2Authorization getByState(String token) {
    Oauth2Authorization dbModel = this.oauth2AuthorizationManager.getByState(token);
    OAuth2Authorization authorization = this.convertToOAuth2Authorization(dbModel);
    return authorization;
  }

  private OAuth2Authorization getByToken(String token) {
    Oauth2Authorization dbModel = this.oauth2AuthorizationManager.getByToken(token);
    OAuth2Authorization authorization = this.convertToOAuth2Authorization(dbModel);
    return authorization;
  }

  private OAuth2Authorization convertToOAuth2Authorization(Oauth2Authorization source) {
    if (source == null) {
      return null;
    }
    String registeredClientId = source.getRegisteredClientId();
    RegisteredClient registeredClient =
        this.registeredClientRepository.findById(registeredClientId);
    if (registeredClient == null) {
      throw new DataRetrievalFailureException(
          "The RegisteredClient with id '"
              + registeredClientId
              + "' was not found in the RegisteredClientRepository.");
    }
    return this.oauth2AuthorizationToOAuth2AuthorizationConverter.convert(source, registeredClient);
  }

  private Oauth2Authorization convertWithOldData(
      OAuth2Authorization authorization, Oauth2Authorization oldAuthorization) {
    Oauth2Authorization dbAuthorization =
        this.oauth2AuthorizationFromOAuth2AuthorizationConverter.convert(authorization);
    if (oldAuthorization != null) {
      dbAuthorization.setCreateTime(oldAuthorization.getCreateTime());
      dbAuthorization.setRowVersion(dbAuthorization.getRowVersion());
      dbAuthorization.setDeleteFlag(oldAuthorization.getDeleteFlag());
    }
    return dbAuthorization;
  }
}
